﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InvokeMethodExpressionTokenizer.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows.Input
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;

    internal static class InvokeMethodExpressionTokenizer
    {
        public static IEnumerable<InvokeMethodExpressionToken> Tokenize(string expression)
        {
            var reader = new TokenReader(new StringReader(expression));

            InvokeMethodExpressionToken token;
            while ((token = reader.NextToken()) != null)
            {
                yield return token;
            }
        }

        private class TokenReader
        {
            private readonly StringReader _reader;
            private char _current;

            public TokenReader(StringReader reader)
            {
                IsBof = true;
                _reader = reader;
            }

            public InvokeMethodExpressionToken NextToken()
            {
                if (IsBof)
                {
                    this.Read();
                    IsBof = false;
                }

                SkipWhiteSpace();
                if (IsEof)
                {
                    return null;
                }

                switch (Current)
                {
                    case '.':
                        return this.ReadConstant(InvokeMethodExpressionToken.Dot);
                    case '*':
                        return this.ReadConstant(InvokeMethodExpressionToken.Star);
                    case '<':
                        return this.ReadConstant(InvokeMethodExpressionToken.LessThan);
                    case '>':
                        return this.ReadConstant(InvokeMethodExpressionToken.GreaterThan);
                    case '?':
                        return this.ReadConstant(InvokeMethodExpressionToken.QuestionMark);
                    case ',':
                        return this.ReadConstant(InvokeMethodExpressionToken.Comma);
                    case '(':
                        return this.ReadConstant(InvokeMethodExpressionToken.OpenBracket);
                    case '{':
                        return this.ReadConstant(InvokeMethodExpressionToken.OpenCurlyBrace);
                    case '}':
                        return this.ReadConstant(InvokeMethodExpressionToken.CloseCurlyBrace);
                    case ')':
                        return this.ReadConstant(InvokeMethodExpressionToken.CloseBracket);
                    default:
                        return ReadIdentifier();
                }
            }

            private InvokeMethodExpressionToken ReadConstant(InvokeMethodExpressionToken token)
            {
                this.Read();
                return token;
            }

            private InvokeMethodExpressionToken ReadIdentifier()
            {
                if (!Char.IsLetter(Current) && "_@".IndexOf(Current) == -1)
                {
                    throw new FormatException("Expression is not in a valid format.");
                }

                var output = new StringBuilder();
                output.Append(Current);
                while (this.Read() && (Char.IsLetterOrDigit(Current) || Current == '_'))
                {
                    output.Append(Current);
                }

                return new InvokeMethodExpressionToken(output.ToString(), InvokeMethodExpressionTokenType.Identifier);
            }

            private bool IsEof { get; set; }

            private bool IsBof { get; set; }

            private void SkipWhiteSpace()
            {
                while (Char.IsWhiteSpace(Current) && this.Read())
                {
                }
            }

            private char Current
            {
                get { return this.IsEof || this.IsBof ? (char)0 : this._current; }
                set { _current = value; }
            }

            private bool Read()
            {
                if (IsEof)
                {
                    return false;
                }

                var readResult = _reader.Read();
                if (readResult == -1 || readResult == 0)
                {
                    IsEof = true;
                    return false;
                }

                Current = (char)readResult;
                return true;
            }
        }
    }
}